/*
 *   This file is part of ed_pvr.
 *
 *   ed_pvr is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   ed_pvr is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with ed_pvr.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <memory.h>
#include <ctype.h>
#include <malloc.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <getopt.h>
#include <math.h>
#include <sys/resource.h>
#include <curl/curl.h>
#include <zlib.h>
#include <pthread.h>
#include "utils.h"
#include "parson.h"
#include "zap2it.h"
#include "timers.h"

#define ZAP_SECS_IN_DAY 		86400
#define ZAP_SECS_IN_HOUR 		3600

#define ZAP_HOURS_PER_FETCH		3
#define ZAP_DEFAULT_CHAN_FILE	"channels.txt"
#define ZAP_MAX_FETCH_THREADS	4

// USA-WA62449-X
#define CURL_USER_AGENT_STR	"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36"
#define ZAP2IT_URL_TEMP_1	"http://www.zap2it.com/tvgrid/_xhr/schedule?time=%"PRId64"&lineupid=%s&zip=%s&tz=UTC"
#define ZAP2IT_URL_TEMP_2	"http://www.zap2it.com/tvgrid/_xhr/schedule?time=%"PRId64"&lineupid=%s&offset=%d&count=%d&zip=%s&tz=UTC"

#define ZAP2IT_GENRE_COUNT		11


#define SEC_IN_DAY 		(3600 * 24)
#define SEC_IN_HOUR 	(3600 * 1)
#define ZAP_BLOCK_TIMES_COUNT 9

static int ZAP_BLOCK_TIMES[ZAP_BLOCK_TIMES_COUNT] =
{
	0, 3, 6, 9, 12, 15, 18, 21, 24,
};
/*
enum ZAP2IT_GENRE_TYPE
{
	ZAP2IT_GENRE_COMEDY, 	// "Show"
	ZAP2IT_GENRE_NEWS,		// "News"
	ZAP2IT_GENRE_LIFESTYLE, // "Lifestyle" -- need to add
	ZAP2IT_GENRE_MOVIES,	// "Movie"
	ZAP2IT_GENRE_DRAMA,		// "Show"
	ZAP2IT_GENRE_SPORTS,	// "Sports"
	ZAP2IT_GENRE_REALITY,	// "Show"
	ZAP2IT_GENRE_MUSIC,		// "Music"
	ZAP2IT_GENRE_DOCU,		// "Educational"
	ZAP2IT_GENRE_CHILDREN,	// "Children"
	ZAP2IT_GENRE_ART,		// "Art" -- need to add
};
*/

static char* ZAP2IT_XBMC_GENRE_MAP[ZAP2IT_GENRE_COUNT] = {
	"Comedy",
	"News",
	"Lifestyle",
	"Movie",
	"Drama",
	"Sports",
	"Reality",
	"Music",
	"Educational",
	"Children",
	"Art",
};

const char *ZAP2IT_GENRE_STUCT[ZAP2IT_GENRE_COUNT] =
{
	"comedy",		// 0x0001
	"news",			// 0x0002
	"lifestyle",	// 0x0004
	"movies",		// 0x0008
	"drama",		// 0x0010
	"sports",		// 0x0020
	"reality",		// 0x0040
	"music",		// 0x0080
	"documentary",	// 0x0100
	"children",		// 0x0200
	"art",			// 0x0400
};

static uint32_t ZAP2IT_GENRE_TYPES[ZAP2IT_GENRE_COUNT] = {
	0x00000001,
	0x00000002,
	0x00000004,
	0x00000008,
	0x00000010,
	0x00000020,
	0x00000040,
	0x00000080,
	0x00000100,
	0x00000200,
	0x00000400,
};


typedef struct {
	JSON_Object *object;
	JSON_Value *value;
	size_t index;
	zap2it_epg_channel_s prev_chan;
	zap2it_single_epg_entry_s prev_epg;

} json_parent_pass;


UTILS_DATAPAIR_STRUCT * _curl_get_url(char *url, char *post_data, int gzip);

uint32_t _zap2it_parse_json(zap2it_epg_s *epg, JSON_Value *root_value, int depth, json_parent_pass *parent, uint32_t *e_count);
zap2it_epg_channel_s * _zap2it_add_channel(zap2it_epg_s *epg, zap2it_epg_channel_s *chan);
int _zap2it_add_single_epg(zap2it_epg_s *epg, zap2it_epg_channel_s *channel, zap2it_single_epg_entry_s *entry);

int _zap2it_fetch_epg_block(zap2it_epg_s *epg, uint64_t start_time, int depth);
void *_zap2it_fetch_thread(void *);
int _zap2it_merge_epg(zap2it_epg_s *epg, zap2it_epg_s *epg2);
zap2it_epg_channel_s * _zap2it_get_channel(zap2it_epg_s *epg, uint64_t channel_num);
void *_zap2it_update_thread(void *p);
TIMER_STRUCT *_zap2it_timer_from_epg(zap2it_epg_channel_s *, zap2it_single_epg_entry_s *);


UTILS_DATAPAIR_STRUCT * _curl_get_url(char *url, char *post_data, int gzip)
{
	UTILS_DATAPAIR_STRUCT *ret = NULL;
	if (url)
	{
		double dlen = 0;
		ret = calloc(1, sizeof(UTILS_DATAPAIR_STRUCT));
		CURL *ch  = NULL;
		CURLcode res = {0};

		if (!ch)
			ch = curl_easy_init();

		curl_easy_setopt(ch, CURLOPT_URL, url);
		curl_easy_setopt(ch, CURLOPT_FAILONERROR, 1);
		curl_easy_setopt(ch, CURLOPT_VERBOSE, 0);
		curl_easy_setopt(ch, CURLOPT_USERAGENT, CURL_USER_AGENT_STR);
		if (gzip)
		{
			curl_easy_setopt(ch, CURLOPT_ACCEPT_ENCODING, "gzip");
			curl_easy_setopt(ch, CURLOPT_HTTP_CONTENT_DECODING, 0);
			curl_easy_setopt(ch, CURLOPT_HTTP_TRANSFER_DECODING, 0);
		}

		curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, generic_curl_handler);
		curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void*)ret);
		if (post_data)
			curl_easy_setopt(ch, CURLOPT_POSTFIELDS, post_data);

		res = curl_easy_perform(ch);
		curl_easy_getinfo(ch, CURLINFO_SIZE_DOWNLOAD, &dlen);
//		printf("Bytes Downloaded: %0.2f\n", dlen);
		curl_easy_cleanup(ch);

		if (res != CURLE_OK)
		{
			if (ret->data)
				free(ret->data);
			free(ret);
			ret = NULL;
		}
	}
	return ret;
}


TIMER_STRUCT *_zap2it_timer_from_epg(zap2it_epg_channel_s *c, zap2it_single_epg_entry_s *tmp_epg)
{
	if (tmp_epg)
	{
		TIMER_STRUCT *new_timer = calloc(1, sizeof(TIMER_STRUCT));
		if (new_timer)
		{
			int q;
			TIMER_STRUCT_EXTRA *extra = calloc(1, sizeof(TIMER_STRUCT_EXTRA));
			new_timer->extra_data = (void*) extra;

			new_timer->data.channel_num = c->channel_num;
			new_timer->data.start_epoch = tmp_epg->start_epoch;
			new_timer->data.end_epoch = tmp_epg->end_epoch;
			if (tmp_epg->is_new)
				snprintf(new_timer->data.title, sizeof(new_timer->data.title) - 1, "%s *",  tmp_epg->prg_title);
			else
				snprintf(new_timer->data.title, sizeof(new_timer->data.title) - 1, "%s",  tmp_epg->prg_title);

			strncpy(new_timer->data.plot_outline, tmp_epg->ep_title, sizeof(new_timer->data.plot_outline) - 1);
			strncpy(new_timer->data.plot, tmp_epg->desc, sizeof(new_timer->data.plot) - 1);
			strncpy(new_timer->data.channel_name, c->channel_name, sizeof(new_timer->data.channel_name) - 1);
			for(q=0; q < ZAP2IT_GENRE_COUNT; q++)
			{
				if (tmp_epg->genre_mask & ZAP2IT_GENRE_TYPES[q])
				{
					if (extra->category_count < TIMER_EXTRA_CAT_MAX)
					{
						strncpy(extra->category[extra->category_count], ZAP2IT_XBMC_GENRE_MAP[q], sizeof(extra->category[extra->category_count]) - 1);
						extra->category_count++;
					}
/*
					if (strlen(new_timer->data.genre) < 2)
					{
//						strncpy(new_timer->data.genre, ZAP2IT_XBMC_GENRE_MAP[q], sizeof(new_timer->data.genre) - 1);
//							printf("\t Genre [%s]\n",new_timer->data.genre);
					}
					else
					{
						strncpy(new_timer->data.sub_genre, ZAP2IT_XBMC_GENRE_MAP[q], sizeof(new_timer->data.sub_genre) - 1);
						printf("\t SubGenre [%s]\n",new_timer->data.sub_genre);
					}
*/
				}
			}
			return new_timer;
		}
	}
	return NULL;
}

TIMER_STRUCT * zap2it_get_single_epg(zap2it_epg_s *epg, uint64_t channel_num, uint64_t start_time, uint64_t end_time)
{
//	TIMER_STRUCT *ret = NULL;

	if (epg && channel_num)
	{
		zap2it_epg_channel_s *c = _zap2it_get_channel(epg, channel_num);
		if (c)
		{
			zap2it_single_epg_entry_s *tmp_epg = c->first_epg_entry;

			while(tmp_epg)
			{
				if (tmp_epg->end_epoch == end_time && tmp_epg->start_epoch == start_time)
					return _zap2it_timer_from_epg(c, tmp_epg);
				tmp_epg = tmp_epg->next;
			}
		}
		else
		{
			printf("zap2it_get_single_epg ugh\n");

		}
	}
	return NULL;
}



TIMER_STRUCT * zap2it_get_epg(zap2it_epg_s *epg, uint64_t channel_num, uint64_t start_time, uint64_t end_time)
{
	TIMER_STRUCT *ret = NULL;

	if (epg && channel_num)
	{
		zap2it_epg_channel_s *c = _zap2it_get_channel(epg, channel_num);
		if (c)
		{
			//zap2it_timer_s *current = NULL;
			TIMER_STRUCT *current = NULL;

			zap2it_single_epg_entry_s *tmp_epg = c->first_epg_entry;
			if (end_time == 0)
				memset(&end_time, 0xF, sizeof(end_time));
//			printf("zap2it_get_epg 1: %" PRId64" -- %" PRId64"\n", start_time, end_time);

			while(tmp_epg)
			{
				if (tmp_epg->end_epoch > start_time && tmp_epg->start_epoch < end_time)
				{
					TIMER_STRUCT *new_timer = _zap2it_timer_from_epg(c, tmp_epg);
					if (new_timer)
					{
//						int q;
//						ret->extra_data = calloc(1, sizeof(TIMER_STRUCT_EXTRA));

						if (ret == NULL)
						{
							current = ret = new_timer;
						}
						else
						{
							current->next = new_timer;
							current = new_timer;
						}

						if (ret->extra_data)
						{
							TIMER_STRUCT_EXTRA * e = (TIMER_STRUCT_EXTRA *)ret->extra_data;
							e->epg_count++;
						}
					}
				}
/*
				else
				{
					printf("Not doing..\n");
				}
*/
				tmp_epg = tmp_epg->next;
			}
		}
		else
		{
			printf("zap2it_get_epg ugh\n");

		}
	}
	return ret;
}

int _zap2it_add_single_epg(zap2it_epg_s *epg, zap2it_epg_channel_s * channel, zap2it_single_epg_entry_s *entry)
{
	if (epg && channel && entry)
	{
		if (channel->current_epg_entry)
		{
			if (entry->start_epoch < channel->current_epg_entry->end_epoch)
			{
				printf("_zap2it_add_single_epg BAD\n");
				return 1;
			}
		}

		zap2it_single_epg_entry_s * new_epg = calloc(1, sizeof(*new_epg));
		memcpy(new_epg, entry, sizeof(*new_epg));

		if (!channel->first_epg_entry)
		{
			channel->first_epg_entry = channel->current_epg_entry = new_epg;
		}
		else
		{
			channel->current_epg_entry->next = new_epg;
			channel->current_epg_entry = channel->current_epg_entry->next;
		}
		channel->epg_count++;
	}
	return 0;
}




//zap2it_epg_channel_s * _zap2it_get_channel(zap2it_epg_s *epg, zap2it_epg_channel_s *chan)
zap2it_epg_channel_s * _zap2it_get_channel(zap2it_epg_s *epg, uint64_t channel_num)
{
	if (epg && channel_num)
	{
		zap2it_epg_channel_s *tmp_chan = epg->first_channel;

		while(tmp_chan)
		{
			if (tmp_chan->channel_num == channel_num)
				return tmp_chan;
			tmp_chan = tmp_chan->next;
		}
	}
	return NULL;
}

zap2it_epg_channel_s * _zap2it_add_channel(zap2it_epg_s *epg, zap2it_epg_channel_s *chan)
{
	if (epg && chan && chan->channel_num)
	{
		zap2it_epg_channel_s *tmp_chan = _zap2it_get_channel(epg, chan->channel_num);
		if (tmp_chan)
			return tmp_chan;
		zap2it_epg_channel_s *new_channel = calloc(1, sizeof(zap2it_epg_channel_s));
		memcpy(new_channel, chan, sizeof(*new_channel));
		if (!epg->first_channel)
		{
			epg->first_channel = epg->current_channel =  new_channel;
		}
		else
		{
			epg->current_channel->next =  new_channel;
			epg->current_channel = epg->current_channel->next;
		}
		return new_channel;
	}
	else
	{
	}
	return NULL;
}


uint32_t _zap2it_parse_json(zap2it_epg_s *epg, JSON_Value *root_value, int depth, json_parent_pass *parent, uint32_t *chan_count)
{
//	uint32_t e_count = chan_count;
	json_parent_pass _parent = {0};
	size_t count, i;
	const char * tmp_name = NULL, *tmp_value=NULL;
	JSON_Object *tmp_obj = json_value_get_object(root_value);

	if (parent)
	{
		tmp_name = json_object_get_name(parent->object,  parent->index);
	}

	switch(json_value_get_type(root_value))
	{
		case JSONObject:
		{
			count = json_object_get_count(tmp_obj);
			for(i=0; i < count; i++)
			{
				tmp_name = json_object_get_name(tmp_obj,  i);
				_parent.object = tmp_obj;
				_parent.value = root_value;
				_parent.index = i;

				if (parent)
				{
					memcpy(&_parent.prev_epg, &parent->prev_epg, sizeof(_parent.prev_epg));
					memcpy(&_parent.prev_chan, &parent->prev_chan, sizeof(_parent.prev_chan));
				}
//				printf("About to1: %s [%d]\n", tmp_name, depth);
				_zap2it_parse_json(epg, json_object_get_value(tmp_obj, tmp_name), depth+1, &_parent, chan_count);

				if (parent)
				{
					memcpy(&parent->prev_epg, &_parent.prev_epg, sizeof(parent->prev_epg));
					memcpy(&parent->prev_chan, &_parent.prev_chan,  sizeof(parent->prev_chan));
				}
			}
			return 0;
		}
		break;

		case JSONArray:
		{
//			printf("ARR: %s\n", );
//			char
			JSON_Array * tmp_arr = json_value_get_array(root_value);
			count = json_array_get_count(tmp_arr);
			for(i=0; i < count; i++)
			{
				JSON_Value * v = json_array_get_value(tmp_arr,  i);
//				char *tmp_name = json_value_get_string(v);
//				printf("About to2: %s - %s [%d]\n", tmp_name, json_value_get_string(v), depth);
				_zap2it_parse_json(epg, v, depth+1, parent, chan_count);
			}
			return 0;
		}
		break;


		case JSONError:
//			printf("JSONError");
		break;
		case JSONNull:
//			printf(": NULL");
		break;

		case JSONNumber:
/*
			if (strcmp(tmp_name, "duration") == 0)
			{
//				parent->prev_epg.duration = json_value_get_number(root_value);
//				parent->prev_epg.end_epoch = parent->prev_epg.start_epoch + floor(parent->prev_epg.duration);
//				printf("\t\t\t---------dur: %0.2f\n", json_value_get_number(root_value));
			}
*/
			if (strcmp(tmp_name, "id") == 0)
			{
//					printf("--------- %0.2f [%d]\n", json_value_get_number(root_value), depth);
				if (depth == 8)
				{
					if (parent->prev_epg.start_epoch)
					{
//						if (!parent->prev_chan.channel_num)
//							printf("FFFFFFFFF: %" PRId64"\n", parent->prev_chan.channel_num);
						zap2it_epg_channel_s * tmp_chan = _zap2it_add_channel(epg, &parent->prev_chan);
						if (tmp_chan)
						{
							_zap2it_add_single_epg(epg, tmp_chan, &parent->prev_epg);
						}
					}
				}
			}
		break;

		case JSONBoolean:
//			if (json_value_get_boolean(root_value) == 0)
//				printf(": false");
//			else
//				printf(": true");
		break;

		case JSONString:
			tmp_value = json_value_get_string(root_value);
			if (strcmp(tmp_name, "callSign") == 0)
			{
				if (parent)
				{
					memset(&parent->prev_chan, 0, sizeof(parent->prev_chan));
				}
				strncpy((char*)&parent->prev_chan.channel_name, tmp_value, sizeof(parent->prev_chan.channel_name) - 1);
//				if (do_count)
//					epg->fetch_count++;
				if (chan_count)
				{
					(*chan_count) = (*chan_count) + 1;
//					printf("Callsign: %s [%d] (c: %d)\n", parent->prev_chan.channel_name, depth, *chan_count);
				}

				int tmpi = strlen(tmp_value);

				if ((tmp_value[tmpi - 2] == 'H' && tmp_value[tmpi - 1] == 'D') ||
					(tmp_value[tmpi - 3] == 'H' && tmp_value[tmpi - 2] == 'D'  && tmp_value[tmpi - 1] == 'P'))
				{
//					printf("HERE: %s\n", tmp_value);
					parent->prev_chan.hd_flag = 1;
				}
//				printf("[%d] e_count1: %d (%s)\n", depth, e_count, parent->prev_chan.channel_name);
			}
			else if (strcmp(tmp_name, "channelNo") == 0)
			{
				parent->prev_chan.channel_num  = strtol(tmp_value, (char **)NULL, 10);
//				printf("\t%s : [%" PRId64"]\n", tmp_name, parent->prev_chan.channel_num);
			}
			else if (strcmp(tmp_name, "stgenres") == 0)
			{
				if (strcasecmp(tmp_value,"hd") == 0)
				{
//					printf("HERE: %s\n", tmp_value);
					parent->prev_chan.hd_flag = 1;
				}
			}
			else if (strcmp(tmp_name, "thumbnail") == 0)
			{
				if (strlen(tmp_value) > 5)
				{
					size_t copyto = sizeof(parent->prev_chan.icon_path);
					char *tmps = strstr(tmp_value, "?w=");
					if (tmps)
					{
						tmps[0] = '\0';
					}
					strncpy((char*)parent->prev_chan.icon_path, tmp_value, copyto - 1);
					tmps[0] = '?';

				}
			}
			else if (strcmp(tmp_name, "startTime") == 0)
			{
//				printf("\n");
				memset(&parent->prev_epg, 0, sizeof(parent->prev_epg));
				parent->prev_epg.start_epoch = utils_str_to_epoch2((char*)tmp_value, 1);
//				printf("qq6 [%" PRId64"]:    [%s] [%" PRId64"]\n", parent->prev_chan.channel_num, tmp_value, parent->prev_epg.start_epoch);
			}
			else if (strcmp(tmp_name, "endTimeDisplay") == 0)
			{
				char * tmp1 = utils_epoch_to_str2(parent->prev_epg.start_epoch, 0);
				if (tmp1)
				{
					tmp1[11] = tmp_value[0];
					tmp1[12] = tmp_value[1];
					tmp1[14] = tmp_value[3];
					tmp1[15] = tmp_value[4];
					parent->prev_epg.end_epoch = utils_str_to_epoch2((char*)tmp1, 1);
//					printf("qq1: %s [%s] [%" PRId64"]\n", tmp1, tmp_value, parent->prev_epg.end_epoch - parent->prev_epg.start_epoch);
//					printf("1[%s] [%s] [%s] O: %s -- %" PRId64" (%" PRId64")\n", parent->prev_chan.channel_name, parent->prev_epg.prg_title, tmp_value, tmp1, parent->prev_epg.end_epoch, parent->prev_epg.start_epoch);
//					if (tmp_value[6] == 'P' && tmp_value[0] != '1' && tmp_value[1] != '2')
					if (tmp_value[6] == 'P')
					{
						if (!(tmp_value[0] == '1' && tmp_value[1] == '2'))
							parent->prev_epg.end_epoch += (12 * 60 * 60);
					}
					else if (tmp_value[6] == 'A' && tmp_value[0] == '1' && tmp_value[1] == '2')
					{
						parent->prev_epg.end_epoch -= (12 * 60 * 60);
					}
//					printf("qq3: %s [%s] [%" PRId64"]\n", tmp1, tmp_value, parent->prev_epg.end_epoch - parent->prev_epg.start_epoch);

					if (parent->prev_epg.end_epoch <  parent->prev_epg.start_epoch)
						parent->prev_epg.end_epoch += (24 * 60 * 60);

//					printf("qq4 [%" PRId64"]: %s [%s] [%" PRId64", %" PRId64"] [%" PRId64"]\n", parent->prev_chan.channel_num, tmp1, tmp_value, parent->prev_epg.end_epoch,parent->prev_epg.start_epoch, parent->prev_epg.end_epoch - parent->prev_epg.start_epoch);
/*
					if (parent->prev_epg.end_epoch - parent->prev_epg.start_epoch > 18000)
					{
//						printf("BELAH: %" PRId64"\n", parent->prev_epg.end_epoch - parent->prev_epg.start_epoch);
					}
*/
//					printf("2[%s] [%s] [%s] O: %s -- %" PRId64" (%" PRId64")\n", parent->prev_chan.channel_name, parent->prev_epg.prg_title, tmp_value, tmp1, parent->prev_epg.end_epoch, parent->prev_epg.start_epoch);
					free(tmp1);
				}
			}
			else if (strcmp(tmp_name, "title") == 0)
			{
				strncpy((char*)&parent->prev_epg.prg_title, tmp_value, sizeof(parent->prev_epg.prg_title) - 1);
//				printf("\t\t%s : [%s]\n", tmp_name, tmp_value);
			}
			else if (strcmp(tmp_name, "description") == 0)
			{
				strncpy((char*)&parent->prev_epg.desc, tmp_value, sizeof(parent->prev_epg.desc) - 1);
//				printf("\t\t%s : [%s]\n", tmp_name, tmp_value);
			}
			else if (strcmp(tmp_name, "tmsid") == 0)
			{
				strncpy((char*)&parent->prev_epg.tmsid, tmp_value, sizeof(parent->prev_epg.tmsid) - 1);
//				printf("\t\t%s : [%s]\n", tmp_name, tmp_value);
			}
			else if (strcmp(tmp_name, "prgtitle") == 0)
			{
				if (strlen(parent->prev_epg.prg_title) < 2)
					strncpy((char*)&parent->prev_epg.prg_title, tmp_value, sizeof(parent->prev_epg.prg_title) - 1);
//				printf("\t\t%s : [%s]\n", tmp_name, tmp_value);
			}
			else if (strcmp(tmp_name, "eptitle") == 0)
			{
				strncpy((char*)&parent->prev_epg.ep_title, tmp_value, sizeof(parent->prev_epg.ep_title) - 1);
//				printf("\t\t%s : [%s]\n", tmp_name, tmp_value);
			}
			else if (strcmp(tmp_name, "flags") == 0)
			{
				if (strcasecmp(tmp_value,"new") == 0)
				{
					parent->prev_epg.is_new = 1;
				}
			}
			else if (strcmp(tmp_name, "genres") == 0)
			{
				int y;
//				printf("Title: %s\n", parent->prev_epg.prg_title);
				for(y=0; y < ZAP2IT_GENRE_COUNT; y++)
				{
					if (strcasecmp(tmp_value, ZAP2IT_GENRE_STUCT[y]) == 0)
					{
//						printf("\t: %s\n", ZAP2IT_GENRE_STUCT[y]);
						parent->prev_epg.genre_mask |= ZAP2IT_GENRE_TYPES[y];
						break;
					}
				}
/*
				if (y >= ZAP2IT_GENRE_COUNT)
				{
						printf("UNK [G] : %s\n", tmp_value);
				}
				else
				{
						printf("[G] : %s\n", tmp_value);
						printf("Gen [%" PRId64"] (%s) 0x%x\n", parent->prev_chan.channel_num, parent->prev_epg.prg_title, parent->prev_epg.genre_mask);
				}
*/
			}
			else if (strcmp(tmp_name, "id") == 0)
			{
//				printf("\t\tID: %s : [%s]\n", tmp_name, tmp_value);
			}
			else
			{
//				printf("\t\t%s : [%s]\n", tmp_name, tmp_value);
			}
		break;

		default:
		break;
	}
//				printf("[%d] e_count2: %d\n", depth, e_count);
	return 0;
}


int _zap2it_fetch_epg_block(zap2it_epg_s *epg, uint64_t start_time, int depth)
{
	if (epg)
	{
		FILE *fp = NULL;
		char tmp[8192] = {0};
		char fname[8192] = {0};
		UTILS_DATAPAIR_STRUCT *data = NULL;
		snprintf(fname, sizeof(fname)-1, "%s/%s_%s_%" PRId64"_%03d_%03d.gz", epg->cache_dir, epg->zap_zip, epg->zap_lineup, start_time, epg->fetch_count*depth, epg->fetch_count);

		if (epg->cache)
		{
			if (file_exists(fname) == 0)
			{
//				printf("cache file exists: %s\n", fname);
				fp = fopen(fname, "rb");
				if (fp)
				{
					int n;
					data = malloc(sizeof(*data));
					if (data)
					{
						data->data_size = get_file_size(fname);
						data->data = malloc(data->data_size);
						if (data->data)
						{
							n = fread(data->data, data->data_size, 1, fp);
							if (n != 1)
							{
								free(data->data);
								free(data);
								data = NULL;
							}
						}
					}
					fclose(fp);
				}
			}
		}

		if (depth == 0)
		{
			snprintf(tmp, sizeof(tmp)-1, ZAP2IT_URL_TEMP_1, start_time, epg->zap_lineup, epg->zap_zip);
//			printf("Start URL: %s\n", tmp);
		}
		else
		{
			snprintf(tmp, sizeof(tmp)-1, ZAP2IT_URL_TEMP_2, start_time, epg->zap_lineup, epg->fetch_count*depth, epg->fetch_count, epg->zap_zip);
		}

		if (data == NULL)
		{
			printf("URL: %s\n", tmp);
			data =  _curl_get_url(tmp, NULL, 1);
		}

		if (data)
		{
			if (data->data)
			{
				//printf(": %s -- %d\n", fname, data->data_size);
				if (data->data[0] == 0x1F && data->data[1] == 0x8B)
				{
					if (epg->cache && file_exists(fname) == -1)
					{
						fp = fopen(fname, "wb");
						if (fp)
						{
							fwrite(data->data, data->data_size, 1, fp);
							fclose(fp);
						}
					}

					UTILS_DATAPAIR_STRUCT *d1 = utils_zlib_decompress(data);
					if (d1)
					{
						free(data->data);
						free(data);
						data = d1;
					}
				}


				JSON_Value *root_value = json_parse_string((const char *)data->data);
				if (root_value)
				{
					uint32_t f = 0;

					_zap2it_parse_json(epg, root_value, 1, NULL, &f);
					if (depth == 0)
					{
						epg->fetch_count = f;
//						printf("EPG fetch count: %d\n", epg->fetch_count);
					}
//					printf("EPG fetch count: %d [%d]\n", epg->fetch_count, f);

					json_value_free(root_value);

					if (depth == 0 || (epg->fetch_count && f == epg->fetch_count))
					{
						_zap2it_fetch_epg_block(epg, start_time, depth + 1);
					}
				}
				else
				{
					printf("json_parse_string, no string\n");
				}
				free(data->data);
			}
			free(data);
		}
		epg->wait = 0;
	}
	return 0;
}

void *_zap2it_fetch_thread(void *p)
{
	zap2it_epg_s *epg = (zap2it_epg_s *)p;
	if (epg)
	{
		//printf("[%d -- %d] Fetch Time: % " PRId64"\n", (24/ZAP_HOURS_PER_FETCH), epg->fetch_days, epg->start_time);
		//epg->wait = 0;
		_zap2it_fetch_epg_block(epg, epg->start_time, 0);
	}
	return NULL;
}

int _zap2it_merge_epg(zap2it_epg_s *epg, zap2it_epg_s *epg2)
{
//	printf("Merge Start\n");
	if (epg && epg2)
	{
		if (!epg->first_channel)
		{
//			printf("Merge, just using the copy...\n");
			epg->first_channel = epg2->first_channel;
			epg->current_channel = epg2->current_channel;
			epg2->first_channel = epg2->current_channel = NULL;
		}
		else
		{
			zap2it_epg_channel_s * tmp_chan = NULL;
			zap2it_epg_channel_s * tmp_chan2 = epg2->first_channel;
			while(tmp_chan2)
			{
				if (tmp_chan2->first_epg_entry)
				{
					tmp_chan = _zap2it_get_channel(epg, tmp_chan2->channel_num);

//					printf("Last Start: %" PRId64", End: %" PRId64" -- New: %" PRId64", %" PRId64 "\n", _last_start, _last_end, tmp_chan2->first_epg_entry->start_epoch, tmp_chan2->first_epg_entry->end_epoch);

					while(	tmp_chan2 && tmp_chan2->first_epg_entry &&
							tmp_chan->current_epg_entry->start_epoch == tmp_chan2->first_epg_entry->start_epoch &&
							tmp_chan->current_epg_entry->end_epoch == tmp_chan2->first_epg_entry->end_epoch)
					{
						zap2it_single_epg_entry_s *delme = tmp_chan2->first_epg_entry;
						tmp_chan2->first_epg_entry = tmp_chan2->first_epg_entry->next;
						free(delme);
					}

/*
					if (tmp_chan2->first_epg_entry)
						printf("\t[%" PRId64"] Last Start: %" PRId64", End: %" PRId64" -- New: %" PRId64", %" PRId64 "\n", tmp_chan2->channel_num, _last_start, _last_end, tmp_chan2->first_epg_entry->start_epoch, tmp_chan2->first_epg_entry->end_epoch);

					else
						printf("THIS IS BAD: %" PRId64" (%"PRId64")\n",  tmp_chan2->channel_num, tmp_chan2->epg_count);
*/
					if (tmp_chan && tmp_chan2->first_epg_entry)
					{
						tmp_chan->current_epg_entry->next = tmp_chan2->first_epg_entry;
						tmp_chan->current_epg_entry = tmp_chan2->current_epg_entry;
					}
					tmp_chan2->first_epg_entry = tmp_chan2->current_epg_entry = NULL;
				}
				else
				{
					printf("NO EPG DATA?: %" PRId64" -- %" PRId64"\n", tmp_chan2->channel_num, epg2->start_time);
				}
				tmp_chan2 = tmp_chan2->next;
			}
		}
	}
//	printf("Merge End\n");
	return 0;
}

int zap2it_fetch_epg(zap2it_epg_s *epg)
{
	if (epg && epg->start_time && epg->fetch_days)
	{
		uint8_t current_thread_count = 0, next_wait_join = 0, thread_ids = 0;
		uint8_t c = 0, j = 0;
		int i;
		zap2it_epg_s tmp_epg[epg->max_thread_count];

		int iter = (24/ZAP_HOURS_PER_FETCH) * epg->fetch_days;

		printf("[ZAP] Fetch days: %d\n", epg->fetch_days);
		for(i=0; i < epg->max_thread_count; i++)
		{
			memset(&tmp_epg[i], 0, sizeof(zap2it_epg_s));
			tmp_epg[i].cache = epg->cache;
		}

		for(i=0; i < iter; i++)
		{
			if (current_thread_count < epg->max_thread_count)
			{
				for(j=0; j < epg->max_thread_count; j++)
				{
					if (tmp_epg[j].thread == 0)
					{
						c = j;
						memset(&tmp_epg[c], 0, sizeof(zap2it_epg_s));

						break;
					}
				}
//				memcpy(&tmp_epg[c], epg, sizeof(zap2it_epg_s));
				tmp_epg[c].cache = epg->cache;
				tmp_epg[c].fetch_days = epg->fetch_days;
				tmp_epg[c].fetch_count = epg->fetch_count;
				memcpy(tmp_epg[c].zap_lineup, epg->zap_lineup, sizeof(tmp_epg[c].zap_lineup) - 1);
				memcpy(tmp_epg[c].zap_zip, epg->zap_zip, sizeof(tmp_epg[c].zap_zip) - 1);
				memcpy(tmp_epg[c].cache_dir, epg->cache_dir, sizeof(tmp_epg[c].cache_dir) - 1);

				tmp_epg[c].start_time = epg->start_time + (i * ZAP_SECS_IN_HOUR * 3);
				tmp_epg[c].wait = 1;
				tmp_epg[c].id = ++thread_ids;
				if (tmp_epg[c].thread == 0)
					pthread_create(&tmp_epg[c].thread, NULL, _zap2it_fetch_thread, (void*)&tmp_epg[c]);
				while(tmp_epg[c].wait)
				{
					usleep(1000*100);
//					printf("Waiting [%d]...\n", c);
				}
//				printf("ThreadID [%ld] [%d]...\n", tmp_epg[c].thread, c);
				current_thread_count++;
			}

			if (current_thread_count >= epg->max_thread_count)
			{
//				printf("Waiting on ID: %d [%d]\n", tmp_epg[next_wait_join].id, next_wait_join);
				pthread_join(tmp_epg[next_wait_join].thread, NULL);

				// Merge here
				_zap2it_merge_epg(epg, &tmp_epg[next_wait_join]);
				zap2it_free_epg_data(&tmp_epg[next_wait_join]);
				memset(&tmp_epg[next_wait_join], 0, sizeof(zap2it_epg_s));
				current_thread_count--;
				next_wait_join++;

				if (next_wait_join >= epg->max_thread_count)
					next_wait_join = 0;
			}
		}
//		printf("Finished launching threads, waiting for them to return\n");


		for(j=0; j < epg->max_thread_count; j++)
		{
			if (tmp_epg[j].thread)
			{
//				printf("Waiting on ID: %d [%d]\n", tmp_epg[j].id, j);
				pthread_join(tmp_epg[j].thread, NULL);
				// Merge here
				_zap2it_merge_epg(epg, &tmp_epg[j]);
				zap2it_free_epg_data(&tmp_epg[j]);
				memset(&tmp_epg[j], 0, sizeof(zap2it_epg_s));
			}
		}
		return 0;
//		printf("\n");
	}
	return 1;
}



void zap2it_free_epg_data(zap2it_epg_s *epg)
{
	if (epg)
	{
		zap2it_single_epg_entry_s *epg_entry = NULL, *epg_del = NULL;
		zap2it_epg_channel_s * chan = epg->first_channel, *chan_del = NULL;

		while (chan)
		{
			epg_entry = chan->first_epg_entry;
			while (epg_entry)
			{
				epg_del = epg_entry;
				epg_entry = epg_entry->next;
				if (epg_del)
					free(epg_del);
			}
			chan_del = chan;
			chan = chan->next;
			if (chan_del)
				free(chan_del);
		}
		epg->first_channel = epg->current_channel = NULL;
	}
	return;
}

void *_zap2it_update_thread(void *p)
{
	if (p)
	{
		zap2it_epg_s *epg = (zap2it_epg_s *)p;
		time_t now = 0;

		printf("[ZAP] Launching zap2it update thread\n");

		epg->last_update_time = 0;

		while (epg->start_time == 0)
		{
			printf("[ZAP] update thread is waiting for start time\n");
			usleep(1000*1000);
		}

		while (epg && epg->die == 0)
		{
			now = time(NULL);
			if (now - epg->last_update_time > epg->update_wait_time)
			{
				zap2it_free_epg_data(epg);
				epg->start_time = zap_get_start_time(time(NULL));
				printf("[ZAP] Update Start Time: %s", ctime((time_t*)&epg->start_time));
				zap2it_fetch_epg(epg);
//				printf("done\n");
				epg->last_update_time = time(NULL);
			}
			else
//				printf("moop\n");

			usleep(1000*1000);
		}
	}
	return NULL;
}


zap2it_epg_s *zap2it_epg_init(uint8_t max_threads, uint8_t update_thread)
{
	zap2it_epg_s * ret = calloc(1, sizeof(zap2it_epg_s));
	if (ret)
	{
		if (max_threads <= 0)
			max_threads = 1;
		ret->max_thread_count = max_threads;
		ret->cache = 1;
		snprintf(ret->cache_dir, sizeof(ret->cache_dir) -1, "%s", "./");
		if (update_thread)
		{
			ret->update_wait_time = ZAP_SECS_IN_DAY;
//			ret->force_update = 1;
			pthread_mutex_init(&ret->update_lock, NULL);
//			printf("[ZAP] Spawning update thread..\n");
			pthread_create(&ret->update_thread, NULL, _zap2it_update_thread, (void*)ret);
		}

	}
	return ret;
}

int64_t zap_get_start_time(time_t now)
{
	int64_t ret = -1;
	char *local_str = NULL;
	if (!now)
		now = time(NULL);

	local_str = utils_epoch_to_str2(now, 1);

	if (local_str)
	{
		int64_t t1;
		int j, h;
		//printf("1: %s\n", local_str);
		t1 = utils_str_to_epoch2(local_str, 0);

		if (local_str[13] == ':' && local_str[16] == ':')
		{
			char tmp[3] = {0};
			tmp[0] = local_str[11];
			tmp[1] = local_str[12];
			h = atoi(tmp);
			local_str[11] = local_str[12] = '0'; 	// H

			local_str[14] = local_str[15] = '0';  	// M
			local_str[17] = local_str[18] = '0'; 	// S

			t1 = utils_str_to_epoch2(local_str, 0);

			for(j=0; j < ZAP_BLOCK_TIMES_COUNT - 1; j++)
			{
				if (h >= ZAP_BLOCK_TIMES[j] && h < ZAP_BLOCK_TIMES[j+1])
					break;
			}

			if (j <= ZAP_BLOCK_TIMES_COUNT - 1)
				ret = t1 + (ZAP_BLOCK_TIMES[j] *3600);
		}
		free(local_str);
	}
	return ret;
}

#ifdef _MAIN_

uint8_t use_cache = 1;
char *chan_file = NULL;
int hd_only = 0;
char *zip = NULL;
char *lineup = NULL;
char *cache_dir = NULL;
int mode = 0;
int max_threads = 1;
int fetch_days = 1;

static const char *optString = "d:c:t:m:z:l:o:hC";

static const struct option longOpts[] =
{
//    { "input", required_argument, NULL, 'i' },
    { NULL, no_argument, NULL, 0 }
};

uint8_t get_opts(int argc, char *const argv[])
{
    int index = 0;
    int opt = 0;

    do
    {
        opt = getopt_long(argc, argv, optString, longOpts, &index);
        switch (opt)
        {
            case 't':
				if (optarg)
				{
					max_threads = atoi(optarg);
					if (max_threads == 0 || max_threads > 8)
						max_threads = 1;
				}
            break;

            case 'm':
				if (optarg)
					mode = atoi(optarg);
            break;

            case 'd':
				if (optarg)
					fetch_days = atoi(optarg);
            break;

            case 'C':
				use_cache = 0;
            break;

            case 'h':
				hd_only = 1;
            break;

            case 'o':
				if (optarg)
					chan_file = strdup(optarg);
            break;

            case 'c':
				if (optarg)
					cache_dir = strdup(optarg);
            break;

            case 'l':
				if (optarg)
					lineup = strdup(optarg);
            break;
            case 'z':
				if (optarg)
					zip = strdup(optarg);
            break;
/*
            case '':
				if (optarg)
				 = strdup(optarg);
            break;
*/
            default:
            break;
        }
    }
    while (opt != -1);

    return 0;
}


int main(int argc, char *argv[])
{
	zap2it_epg_s *epg = NULL;
//	zap2it_single_epg_entry_s *zap_single_epg = NULL;

    get_opts(argc, argv);

	if (!mode)
	{
		printf("Must specify mode (1 = generate channels file, 2 = ?)\n");
		exit(1);
	}

	if (!zip || !lineup)
	{
		printf("Must specify zip (-z 98033) and lineup (-l USA-WA62449-X)\n");
		exit(1);
	}

	if (!chan_file)
		chan_file = ZAP_DEFAULT_CHAN_FILE;

	switch (mode)
	{
		case 1:
		{
			FILE *fp = fopen(chan_file, "w");
			if (fp)
			{
//				char tmp[4096] = {0};
				epg = zap2it_epg_init(max_threads, 1);
				if (epg)
				{
					epg->cache = use_cache;

					printf("Generating [zip: %s] [lineup: %s] [HD only: %d] [Cache: %d]\n", zip, lineup, hd_only, epg->cache);
					printf("Writing to: %s\n", chan_file);

					epg->fetch_days = fetch_days;
					strncpy(epg->zap_lineup, lineup, sizeof(epg->zap_lineup) - 1);
					strncpy(epg->zap_zip, zip, sizeof(epg->zap_zip) - 1);
					if (cache_dir)
						strncpy(epg->cache_dir, cache_dir, sizeof(epg->cache_dir) - 1);

					epg->start_time = zap_get_start_time(time(NULL));

					printf("Start Time: %s", ctime((time_t*)&epg->start_time));
					printf("Cache Dir: %s\n", epg->cache_dir);

					usleep(1000*1000*300);
/*
					zap2it_fetch_epg(epg);

					zap2it_epg_channel_s * chan = epg->first_channel;
					while (chan)
					{
						//snprintf(tmp, sizeof(tmp)-1, "%" PRId64",%" PRId64",%s,%s\n", chan->channel_num, chan->channel_num, chan->icon_path, chan->channel_name);
						if (!hd_only || (hd_only && chan->hd_flag))
						{
							fprintf(fp, "%" PRId64",%" PRId64",%s,%s\n", chan->channel_num, chan->channel_num, chan->icon_path, chan->channel_name);
						}
						else if (hd_only)
						{
							printf("Skipping non-HD channel: %" PRId64" (%s)\n", chan->channel_num, chan->channel_name);
						}
						chan = chan->next;
					}
*/
//					zap2it_timer_s * timer_data = zap2it_get_epg(epg, 654, time(NULL), time(NULL) + (5*ZAP_SECS_IN_DAY));
					TIMER_STRUCT * timer_data = zap2it_get_epg(epg, 654, time(NULL), 0);
					if (timer_data)
					{
					}
					printf("Freeing... ");
					zap2it_free_epg_data(epg);
					printf("done\n");
					free(epg);
				}
				fclose(fp);
			}
		}
		break;

		case 2:
		break;
	}
	return 0;
}
#endif
